﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Obra.Common;

namespace Obra.Sync
{
    public class FileSystemServiceProvider
        : IFileSystemService
    {
        /// <summary>
        /// c-tor
        /// </summary>
        public FileSystemServiceProvider()
        {
        }

        /// <summary>
        /// List of known media files supported by SmugMug.
        /// </summary>
        private readonly List<string> mediaFileExtensions = new List<string>()
        {
            ".avi",  // video
            ".gif",
            ".jpeg", 
            ".jpg",
            ".m4a",  // video
            ".m4v",  // video
            ".mov",  // video
            ".mpeg", // video
            ".mpg",  // video
            ".png",
        };

        /// <summary>
        /// Returns true if this file is a media file.
        /// </summary>
        /// <param name="fileInfo">
        /// File to check if it is a file info.
        /// </param>
        /// <remarks>
        /// <para>
        /// A file is a media file if the following things are true.
        ///   1. File is not a directory.
        ///   2. File has an extension.
        ///   3. File extension is not in the list of pre-defined media extenions.
        /// </para>
        /// </remarks>
        private bool IsMediaFile(FileInfo fileInfo)
        {
            if (FileAttributes.Directory == (fileInfo.Attributes & FileAttributes.Directory))
            {
                return false;
            }
            
            if (String.IsNullOrEmpty(fileInfo.Extension))
            {
                return false;
            }

            return this.mediaFileExtensions.Contains(
                fileInfo.Extension,
                StringComparer.OrdinalIgnoreCase);
        }

        #region IFileSystemService

        /// <summary>
        /// Compute the checksum of a file.
        /// </summary>
        /// <param name="path">
        /// Path to the file to compute the checksum of.
        /// </param>
        /// <param name="checksumAlgorithm">
        /// Algorithm to use for the checksum computation.
        /// </param>
        /// <returns>
        /// Checksum as hex digits, i.e. 0123456789abcdef
        /// </returns>
        public string ComputeChecksum(
            string path,
            ChecksumAlgorithm checksumAlgorithm)

        {
            using (FileStream stream = new FileStream(path, FileMode.Open))
            {
                Checksum checksum = new Checksum(
                    stream,
                    checksumAlgorithm);

                return checksum.ToString();
            }
        }

        /// <summary>
        /// Delete the specified file.
        /// </summary>
        /// <param name="path">
        /// File to delete.
        /// </param>
        public void Delete(string path)
        {
            string message = String.Format(Resource.DeleteFileTemplate,
                path);

            Log.Info(
                message, 
                LogCategory.FileSystem);

            File.Delete(path);
        }

        /// <summary>
        /// Get the files in a directory.
        /// </summary>
        /// <param name="directory">
        /// The directory to query.
        /// </param>
        public List<FileInfo> GetMediaFiles(
            string path)
        {
            var files = from directory in Directory.GetDirectories(path)
                        from file in new DirectoryInfo(directory).GetFiles()
                        where this.IsMediaFile(file)
                        select file;

            return files.ToList();
        }

        /// <summary>
        /// Return a function that returns a stream.
        /// </summary>
        /// <param name="path">
        /// File whose strea is returned.
        /// </param>
        /// <remarks>
        /// <para>
        /// The synchronization framework will most likely cause unnecessary 
        /// loads, which would cause the loading of streams.  Loading a stream
        /// every time is unnecessary, so lazily load the stream by wrapping it
        /// in a function.
        /// </para>
        /// </remarks>
        public Func<Stream> LazyLoad(string path)
        {
            Func<Stream> function = delegate()
            {
                return new FileStream(path, FileMode.Open);
            };

            return function;
        }

        private const int STREAM_BUFFER_SIZE = 8192;

        /// <summary>
        /// Write the stream to the specified path.
        /// </summary>
        /// <param name="path">
        /// File to write the contents of the stream to.
        /// </param>
        /// <param name="stream">
        /// Data to write to the specified file.
        /// </param>
        public void Write(
            string path,
            Stream stream)
        {
            string message = String.Format(Resource.SaveFileTemplate,
                path);

            Log.Debug(
                message,
                LogCategory.FileSystem);

            using (FileStream outStream = new FileStream(path, FileMode.Create))
            {
                int bytesRead;
                byte[] buffer = new byte[STREAM_BUFFER_SIZE];

                while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    outStream.Write(buffer, 0, bytesRead);
                }
            }
        }

        #endregion IFileSystemService
    }
}
